define(["dojo/on", "dojo/query"],
function(on, query){
	// This module exposes useful functions for working with touch devices.
	
	var util = {
		// Overridable defaults related to extension events defined below.
		tapRadius: 10,
		dbltapTime: 250,
		
		selector: function(selector, eventType, children){
			// summary:
			//		Reimplementation of on.selector, taking an iOS quirk into account
			return function(target, listener){
				var bubble = eventType.bubble;
				if(bubble){
					// the event type doesn't naturally bubble, but has a bubbling form, use that
					eventType = bubble;
				}else if(children !== false){
					// for normal bubbling events we default to allowing children of the selector
					children = true;
				}
				return on(target, eventType, function(event){
					var eventTarget = event.target;
					
					// iOS tends to report the text node an event was fired on, rather than
					// the top-level element; this may end up causing errors in selector engines
					if(eventTarget.nodeType == 3){ eventTarget = eventTarget.parentNode; }
					
					// there is a selector, so make sure it matches
					while(!query.matches(eventTarget, selector, target)){
						if(eventTarget == target || !children || !(eventTarget = eventTarget.parentNode)){ // intentional assignment
							return;
						}
					}
					return listener.call(eventTarget, event);
				});
			};
		},
		
		countCurrentTouches: function(evt, node){
			// summary:
			//		Given a touch event and a DOM node, counts how many current touches
			//		presently lie within that node.  Useful in cases where an accurate
			//		count is needed but tracking changedTouches won't suffice because
			//		other handlers stop events from bubbling high enough.
			
			if(!("touches" in evt)){
				// Not a touch event (perhaps called from a mouse event on a
				// platform supporting touch events)
				return -1;
			}
			
			var i, numTouches, touch;
			for(i = 0, numTouches = 0; (touch = evt.touches[i]); ++i){
				if(node.contains(touch.target)){
					++numTouches;
				}
			}
			return numTouches;
		}
	};
	
	function handleTapStart(target, listener, evt, prevent){
		// Common function for handling tap detection.
		// The passed listener will only be fired when and if a touchend is fired
		// which confirms the overall gesture resembled a tap.
		
		if(evt.targetTouches.length > 1){ return; } // ignore multitouch
		
		var start = evt.changedTouches[0],
			startX = start.screenX,
			startY = start.screenY;
		
		prevent && evt.preventDefault();
		
		var endListener = on(target, "touchend", function(evt){
			var end = evt.changedTouches[0];
			if(!evt.targetTouches.length){
				// only call listener if this really seems like a tap
				if(Math.abs(end.screenX - startX) < util.tapRadius &&
						Math.abs(end.screenY - startY) < util.tapRadius){
					prevent && evt.preventDefault();
					listener.call(this, evt);
				}
				endListener.remove();
			}
		});
	}
	
	function tap(target, listener){
		// Function usable by dojo/on as a synthetic tap event.
		return on(target, "touchstart", function(evt){
			handleTapStart(target, listener, evt);
		});
	}
	
	function dbltap(target, listener){
		// Function usable by dojo/on as a synthetic double-tap event.
		var first, timeout;
		
		return on(target, "touchstart", function(evt){
			if(!first){
				// first potential tap: detect as usual, but with specific logic
				handleTapStart(target, function(evt){
					first = evt.changedTouches[0];
					timeout = setTimeout(function(){ first = timeout = null; }, util.dbltapTime);
				}, evt);
			}else{
				handleTapStart(target, function(evt){
					// bail out if first was cleared between 2nd touchstart and touchend
					if(!first){ return; }
					var second = evt.changedTouches[0];
					// only call listener if both taps occurred near the same place
					if(Math.abs(second.screenX - first.screenX) < util.tapRadius &&
							Math.abs(second.screenY - first.screenY) < util.tapRadius){
						timeout && clearTimeout(timeout);
						first = timeout = null;
						listener.call(this, evt);
					}
				}, evt, true);
			}
		});
	}
	
	util.tap = tap;
	util.dbltap = dbltap;
	
	return util;
});